// Bluetooth Device Page:
// https://github.com/boskokg/flutter_blue_plus/blob/master/lib/src/bluetooth_device.dart

part of 'win.dart';

class BluetoothDeviceWindows extends FBP.BluetoothDevice {
  BluetoothDeviceWindows({required super.remoteId});

  // used for 'servicesStream' public api
  final _services = StreamController<List<BluetoothServiceWindows>>.broadcast();

  // used for 'isDiscoveringServices' public api
  final _isDiscoveringServices = _StreamController(initialValue: false);

  String get _address => remoteId.str.toLowerCase();

  // 私有变量用于存储当前的连接状态
  BluetoothConnectionState _state = BluetoothConnectionState.disconnected;

  // StreamController 用于管理状态变化的流
  final StreamController<BluetoothConnectionState> _state_controller = StreamController<BluetoothConnectionState>.broadcast();

  Stream<BluetoothConnectionState> get connectionState => _state_controller.stream;

  void _updateState(BluetoothConnectionState newState) {
    if (_state != newState) {
      _state = newState;
      _state_controller.add(newState);
    }
  }

  /// Create a device from an id
  ///   - to connect, this device must have been discovered by your app in a previous scan
  ///   - iOS uses 128-bit uuids the remoteId, e.g. e006b3a7-ef7b-4980-a668-1f8005f84383
  ///   - Android uses 48-bit mac addresses as the remoteId, e.g. 06:E5:28:3B:FD:E0
  static FBP.BluetoothDevice fromId(String remoteId) {
    if (Platform.isWindows) {
      return BluetoothDeviceWindows(remoteId: DeviceIdentifier(remoteId));
    }
    return FBP.BluetoothDevice.fromId(remoteId);
  }

  /// platform name
  /// - this name is kept track of by the platform
  /// - this name usually persist between app restarts
  /// - iOS: after you connect, iOS uses the GAP name characteristic (0x2A00)
  ///        if it exists. Otherwise iOS use the advertised name.
  /// - Android: always uses the advertised name
  String get platformName => FlutterBluePlusWin._platformNames[remoteId] ?? "";

  /// Advertised Named
  ///  - this is the name advertised by the device during scanning
  ///  - it is only available after you scan with FlutterBluePlus
  ///  - it is cleared when the app restarts.
  ///  - not all devices advertise a name
  String get advName => FlutterBluePlusWin._advNames[remoteId] ?? "";

  // stream return whether or not we are currently discovering services
  @Deprecated("planed for removal (Jan 2024). It can be easily implemented yourself") // deprecated on Aug 2023
  Stream<bool> get isDiscoveringServices => _isDiscoveringServices.stream;

  /// Get services
  ///  - returns empty if discoverServices() has not been called
  ///    or if your device does not have any services (rare)
  List<BluetoothServiceWindows> get servicesList => FlutterBluePlusWin._knownServices[remoteId] ?? [];

  /// Stream of bluetooth services offered by the remote device
  ///   - this stream is only updated when you call discoverServices()
  @Deprecated("planed for removal (Jan 2024). It can be easily implemented yourself") // deprecated on Aug 2023
  Stream<List<BluetoothService>> get servicesStream {
    if (FlutterBluePlusWin._knownServices[remoteId] != null) {
      return _services.stream.newStreamWithInitialValue(
        FlutterBluePlusWin._knownServices[remoteId]!,
      );
    } else {
      return _services.stream;
    }
  }

  /// Register a subscription to be canceled when the device is disconnected.
  /// This function simplifies cleanup, so you can prevent creating duplicate stream subscriptions.
  ///   - this is an optional convenience function
  ///   - prevents accidentally creating duplicate subscriptions on each reconnection.
  ///   - [next] if true, the the stream will be canceled only on the *next* disconnection.
  ///     This is useful if you setup your subscriptions before you connect.
  ///   - [delayed] Note: This option is only meant for `connectionState` subscriptions.
  ///     When `true`, we cancel after a small delay. This ensures the `connectionState`
  ///     listener receives the `disconnected` event.
  void cancelWhenDisconnected(StreamSubscription subscription, {bool next = false, bool delayed = false}) {
    if (isConnected == false && next == false) {
      subscription.cancel(); // cancel immediately if already disconnected.
    } else if (delayed) {
      FlutterBluePlusWin._delayedSubscriptions[remoteId] ??= [];
      FlutterBluePlusWin._delayedSubscriptions[remoteId]!.add(subscription);
    } else {
      FlutterBluePlusWin._deviceSubscriptions[remoteId] ??= [];
      FlutterBluePlusWin._deviceSubscriptions[remoteId]!.add(subscription);
    }
  }

  /// Returns true if this device is currently connected to your app
  bool get isConnected {
    return _state == BluetoothConnectionState.connected;
  }

  /// Returns true if this device is currently disconnected from your app
  bool get isDisconnected => isConnected == false;

  //方法立即返回，只有监听回调结果才能认为连接成功或失败
  Future<void> connect({
    Duration timeout = const Duration(seconds: 35), // TODO: implementation missing
    bool autoConnect = false, // TODO: implementation missing
    int? mtu = 512, // TODO: implementation missing
  }) async {
    try {
      _updateState(BluetoothConnectionState.connecting);
      //调用原生交互
      await WinMethodChannelBlue.instance.connect(remoteId);
      //先添加到缓存，后续根据状态移除
      FlutterBluePlusWin._devices[remoteId] = this;
      //等待回调结果
      await connectionState.first.timeout(timeout);
      // request larger mtu
      //无法设置，只能在发现服务后才能设置
      // if (isConnected && mtu != null) {
      //   await requestMtu(mtu);
      // }
    } catch (e) {
      _updateState(BluetoothConnectionState.disconnected);
      log(e.toString());
    }
  }

  Future<void> disconnect({
    int androidDelay = 2000,
    int timeout = 35,
    bool queue = true,
  }) async {
    try {
      await WinMethodChannelBlue.instance.disconnect(remoteId);
    } catch (e) {
      log(e.toString());
    } finally {
      _updateState(BluetoothConnectionState.disconnected);
      FlutterBluePlusWin._devices.remove(remoteId);

      FlutterBluePlusWin._deviceSubscriptions[remoteId]?.forEach((s) => s.cancel());
      FlutterBluePlusWin._deviceSubscriptions.remove(remoteId);
      // use delayed to update the stream before we cancel it
      Future.delayed(Duration.zero).then((_) {
        FlutterBluePlusWin._delayedSubscriptions[remoteId]?.forEach((s) => s.cancel());
        FlutterBluePlusWin._delayedSubscriptions.remove(remoteId);
      });
    }
  }

  Future<List<BluetoothService>> discoverServices({
    bool subscribeToServicesChanged = true, // TODO: implementation missing
    int timeout = 15, // TODO: implementation missing
  }) async {
    List<BluetoothServiceWindows> result = List.from(FlutterBluePlusWin._knownServices[remoteId] ?? []);
    try {
      _isDiscoveringServices.add(true);
      // 调用原生交互
      await WinMethodChannelBlue.instance.discoverServices(remoteId);
      //等待回调发现服务
      final resultX = (await FlutterBluePlusWin._discoverServiceStream.firstWhere((it) => it.deviceId == remoteId).timeout(Duration(seconds: timeout)));
      if (resultX.success) {
        result = resultX.service;
        _services.add(result);
      } else {
        throw Exception("discoverServices error");
      }
    } finally {
      _isDiscoveringServices.add(false);
    }
    return result;
  }

  DisconnectReason? get disconnectReason {
    // TODO: nothing to do
    return null;
  }

  Stream<int> get mtu {
    return FlutterBluePlusWin._mtuConfigStream.where((it) => it.deviceId == remoteId).map((it) => it.mtu).newStreamWithInitialValue(FlutterBluePlusWin._mtuCache[remoteId] ?? 23);
  }

  Future<int> readRssi({int timeout = 15}) async {
    return FlutterBluePlusWin._rssiMap[remoteId] ?? -100;
  }

  Future<int> requestMtu(
    int desiredMtu, {
    double predelay = 0.35,
    int timeout = 15,
  }) async {
    //原生交互
    //生成一个全是FF的字节数组个数是desiredMtu
    BluetoothCharacteristicWindows? characteristic = servicesList.firstOrNull?.characteristics.firstOrNull;
    for (var s in servicesList) {
      for (var c in s.characteristics) {
        final cw = c;
        if (cw.properties.read || cw.properties.write || cw.properties.writeWithoutResponse || cw.properties.notify) {
          characteristic = cw;
          break;
        }
      }
      if (characteristic != null) break;
    }
    if (characteristic != null) {
      await characteristic.write(List<int>.filled(desiredMtu, 0xFF), withoutResponse: true);
      //等待回调结果
      // try {
      //    FlutterBluePlusWin._mtuConfigStream.where((it) => it.deviceId == remoteId).timeout(Duration(seconds: 3)).first;
      // } catch (e) {}
    }
    //再次使用原生交互
    return await WinMethodChannelBlue.instance.requestMtu(remoteId, desiredMtu).timeout(Duration(seconds: timeout));
  }

  Future<void> requestConnectionPriority({
    required ConnectionPriority connectionPriorityRequest,
  }) async {
    // TODO: nothing to do
    return;
  }

  /// Set the preferred connection (Android Only)
  ///   - [txPhy] bitwise OR of all allowed phys for Tx, e.g. (Phy.le2m.mask | Phy.leCoded.mask)
  ///   - [txPhy] bitwise OR of all allowed phys for Rx, e.g. (Phy.le2m.mask | Phy.leCoded.mask)
  ///   - [option] preferred coding to use when transmitting on Phy.leCoded
  /// Please note that this is just a recommendation given to the system.
  Future<void> setPreferredPhy({
    required int txPhy,
    required int rxPhy,
    required PhyCoding option,
  }) async {
    // TODO: implementation missing
  }

  Future<void> createBond({int timeout = 90, Uint8List? pin}) async {
    try {
      // await WinBle.pair(_address);
    } catch (e) {
      log(e.toString());
    }
  }

  Future<void> removeBond({
    int timeout = 30, // TODO: implementation missing
  }) async {
    try {
      // await WinBle.unPair(_address);
    } catch (e) {
      log(e.toString());
    }
  }

  Future<void> clearGattCache() async {
    // TODO: implementation missing
  }

  @override
  bool operator ==(Object other) => identical(this, other) || (other is BluetoothDeviceWindows && runtimeType == other.runtimeType && remoteId == other.remoteId);

  @override
  int get hashCode => remoteId.hashCode;

  @override
  String toString() {
    return 'BluetoothDevice{'
        'remoteId: $remoteId, '
        'platformName: $platformName, '
        'services: ${FlutterBluePlusWin._knownServices[remoteId]}'
        '}';
  }

  @Deprecated('Use createBond() instead')
  Future<void> pair() async => await createBond();

  @Deprecated('Use remoteId instead')
  DeviceIdentifier get id => remoteId;

  @Deprecated('Use localName instead')
  String get name => localName;

  @Deprecated('Use connectionState instead')
  Stream<BluetoothConnectionState> get state => connectionState;

  @Deprecated('Use servicesStream instead')
  Stream<List<BluetoothService>> get services => servicesStream;
}
